<?php

namespace app\index\controller;

use app\index\model\Device;
use app\index\model\Door;
use app\index\model\GearRecord;
use app\index\model\MemberRecord;
use app\index\model\Rules;
use app\index\model\UnusualRecord;
use app\index\model\User;
use think\Controller;
use think\Db;

/**
 * 开关门操作类
 * @package app\index\controller
 */
class Dooroperate extends Controller
{
    private $error = null;

    public function getError()
    {
        return $this->error;
    }

    /**
     * 获取测试指令
     * @param string $data 指令数据部分 68 0a 00 68 data 0a 16
     * @return string 返回完整指令
     */
    public function get_test_code($data)
    {
        $data = str_replace(' ','',$data);
        // 计算数据部分长度，拼接方式：[长度低][长度高]
        $data_len = strlen($data) / 2;
        $Hexlen = base_convert($data_len, 10, 16);
        $Hexlen = str_pad($Hexlen, 4, '0', STR_PAD_LEFT);
        $len_arr = str_split($Hexlen, 2);
        krsort($len_arr);
        $data_len = implode($len_arr);

        // 数据校验和
        $check = $this->checksum($data);

        // [68][长度低][长度高][68] ...[data]... [check][16]
        $code = '68' . $data_len . '68' . $data . $check . '16';

        return $code;
    }

    /**
     * 刷新设备充电状态 // TODO 检查并更新设备充电状态，请调用此接口:（间隔不小于30秒） http://www.example.com/index/dooroperate/refresh_charge_status
     */
    public function refresh_charge_status()
    {
        // 查找最后充电时间已过的柜子
        $door_id = Door::whereTime('last_battery_full','<','now')->where('charger_status',1)->column('id');

        // 更新这些柜子充电状态为未充电
        Door::whereIn('id',$door_id)->update(['charger_status'=>0]);

        // 更新柜子里的设备充电状态
        Device::whereIn('door_id',$door_id)->where('status',2)->update(['status'=>3]); // 状态:1使用中,2充电中,3空闲中,4安检中,5超时未安检,6已报废

        exit('success');
    }

    /**
     * 刷新柜门和设备状态  TODO 刷新柜子状态，请调用此接口：（间隔不小于30秒） http://www.example.com/index/dooroperate/refresh_door_status
     */
    public function refresh_door_status()
    {
        // 找一个要检查的柜子
        $door = Door::where('next_check_device_num',1)->where('device_name','not null')->find();
        if (!$door) {
            $door = Door::order('id','asc')->where('device_name','neq',null)->find();
        }
        $opt = new Dooroperate();
        $opt->getDoorStatus($door->id);

        // 指定下一个要检查的柜子
        $door->next_check_device_num = 0;
        $door->save();
        Door::where('id','>',$door->id)->order('id','asc')->update(['next_check_device_num'=>1]);
        exit('success');
    }

    /**
     * 检查超时未关的柜门和超时未还的设备 TODO 超时检查，请调用此接口：（间隔不小于10秒） http://www.example.com/index/dooroperate/check_timeout
     */
    public function check_timeout()
    {
        // 查询使用中的设备
        $device_list = Device::all(['status'=>1]); // 状态:1使用中,2充电中,3空闲中,4安检中,5超时未安检,6已报废
        foreach ($device_list as $dev_item) {
            // 检查最后一次取用记录
            $check_opt = GearRecord::where('device',$dev_item->id)->order('id','desc')->find();

            // 最后一次取用超过指定个小时前则创建异常记录
            $device_use_time = Rules::where('name','device_use_time')->field('value')->find();
            if (!empty($device_use_time)) {
                if (is_float($device_use_time)) {
                    $t = 60 * $device_use_time;
                    $check_noback_time = strtotime("-$t minute");
                }else{
                    $check_noback_time = strtotime("-$device_use_time hour");
                }
            }else{
                $check_noback_time = strtotime("-10 hour"); // 默认10小时
            }
            if (1 == $check_opt->action && $check_opt->createtime < $check_noback_time) {
                // 检查异常信息是否记录过
                $check_exist = UnusualRecord::get([
                    'type' => 1,
                    'device_id' => $check_opt->device,
                    'user_id' => $check_opt->uid,
                    'gear_id' => db('door')->where('id', $check_opt->gear_door)->value('gear_id'),
                    'door_id' => $check_opt->gear_door,
                    'read' => 0,
                ]);
                // 记录过则不再记录
                if ($check_exist)
                    continue;

                UnusualRecord::create([
                    'type' => 1,
                    'device_id' => $check_opt->device,
                    'user_id' => $check_opt->uid,
                    'gear_id' => db('door')->where('id', $check_opt->gear_door)->value('gear_id'),
                    'door_id' => $check_opt->gear_door,
                ]);
            }
        }


        // 查询开启状态的柜子
        $door_list = Door::all(['lock_status'=>1]); // 开启状态:0关,1开

        foreach ($door_list as $door_item) {
            // 检查最后一次取用记录
            $check_opt = MemberRecord::where('gear_door',$door_item->id)->order('id','desc')->find();

            // 最后一次开门超过10分钟前则创建异常记录
            $door_not_close_time = Rules::where('name','door_not_close_time')->field('value')->find();
            if (!empty($door_not_close_time)) {
                $check_noclose_time = strtotime("-$device_use_time minute");
            }else{
                $check_noclose_time = strtotime("-10 minute"); // 默认10分钟
            }
            if (1 == $check_opt['type'] && $check_opt->create_time < $check_noclose_time) {
                // 检查异常信息是否记录过
                $check_exist = UnusualRecord::get([
                    'type' => 3,
                    'user_id' => $check_opt->uid,
                    'gear_id' => db('door')->where('id', $check_opt->gear_door)->value('gear_id'),
                    'door_id' => $check_opt->gear_door,
                    'read' => 0,
                ]);
                // 记录过则不再记录
                if ($check_exist)
                    continue;

                UnusualRecord::create([
                    'type' => 3,
                    'user_id' => $check_opt->uid,
                    'gear_id' => db('door')->where('id', $check_opt->gear_door)->value('gear_id'),
                    'door_id' => $check_opt->gear_door,
                ]);
            }
        }
        exit('success');
    }

    /**
     * 开柜门
     * @param int $id 柜门ID
     * @param int $userid 用户Id
     * @param int $operation 开柜意图，1使用设备,2设备安检,3其他
     * @param int $isadmin 是后台操作，默认false
     */
    public function openDoor($door_id, $userid, $operation = 1, $isadmin = false)
    {
        $user = $isadmin ? \app\index\model\Admin::get($userid) : User::get($userid);
        $door = Door::get($door_id);
        if (empty($door)) {
            $this->error = '指定的柜子不能使用';
            return false;
        }
        
        // 查询柜体
        $gear = db('gear')->where($door->gear_id)->find();
        if (empty($gear) || $gear['gear_serial'] < 1 || $gear['gear_serial'] > 6) {
            $this->error = '柜体ID有误';
            return false;
        }
        // 开门
        $open_res = $this->open($gear['gear_serial'],$door->door_serial, $user->username, $isadmin ? $user->id : $user->user_serial);

        if (!$open_res) return false;

        // 在柜门中记录本次开门信息
        Door::update([
            'last_operator_id' => $userid,
            'last_operator_isadmin' => $isadmin ? 1 : 0,
            'last_operation' => $operation,
            'lock_status' => 1,
        ], ['id' => $door_id]);

        // 非管理员操作，则记录用户开门
        if (!$isadmin) {
            MemberRecord::create([
                'uid' => $userid,
                'type' => 1,
                'gear_door' => $door_id,
                'is_admin' => $isadmin ? 1 : 0
            ]);
        }
        return true;
    }

    /**
     * 测试发送消息
     * @param string $data 消息数据部分
     * @param bool $program_check 是否检查通信程序，默认检查
     */
    public function msgTest($data, $program_check = true)
    {
        // 数据校验
        $res = $this->sendMsg($data,$program_check);
        if (!$res) {
            de($this->getError());
        }else{
            de('success');
        }
    }

    /**
     * 查询柜门状态
     * @param int $door_id 柜门ID
     */
    public function getDoorStatus($door_id = null)
    {
        // 上位机下发：[68][长度低][长度高][68][4B][03][门编号][校验和][16]
        // 示例：68 03 00 68 4B 03 01 4F 16
        //
        // 解析：
        // 68 帧头
        // 03 数据长度[低位]
        // 00 数据长度[高位]
        // 68 帧头
        // 4B 上下行：4B：上位机下发，8B：下位机上报
        // 03 操作类别：01：开门操作，03：关门上报/查询操作
        // 01 门编号
        // 4F 校验和
        // 16 结束符

        $door_id = $door_id ?: input('door_id');
        if (empty($door_id)) {
            $this->error = '参数错误';
            return false;
        }
        // 查询柜门信息
        $door_info = Door::get($door_id);
        if (empty($door_info)) {
            $this->error = '柜门信息有误';
            return false;
        }
        // 查询柜体信息
        $gear_serial = db('gear')->where('id', $door_info->gear_id)->value('gear_serial');
        if (empty($gear_serial)) {
            $this->error = '柜门信息有误';
            return false;
        }

        $data = "4b03" . str_pad(dechex($door_info->door_serial), 2, '0', STR_PAD_LEFT); // 拼接结果示例：4b0301
        $res = $this->sendMsg($gear_serial, $data);
        return boolval($res);
    }

    /**
     * 接收消息上报
     * @param string $port 端口号
     * @param string $msg 消息内容
     * @return mixed
     */
    public function msgReport($port = null,$msg = null)
    {
        dta($msg,"业务系统收到[COM{$port}]消息");
        if (empty($port)) {
            echo "\n" . "下位机未上报接口出错" . "\n";
            exit;
        }
        $port = intval($port); // 即gear_serial
        // 查询柜体ID
        $gear = db('gear')->where('gear_serial',$port)->find();
        if (empty($gear)) {
            echo "\n" . "下位机未上报接口出错" . "\n";
            exit;
        }

        $msg = strtolower($msg);

        // 数据校验
        $arrData = $this->dataCheck($msg);
        if (!$arrData) {
            echo "\n" . $this->getError() . "\n";
            exit;
        }

        switch ($arrData[1]) {
            case '01' : // 开门上报
                return $this->report_door_open($gear['id'],$arrData);
                break;
            case '03' : // 查询上报
                return $this->report_door_status($gear['id'],$arrData);
                break;
            default :
                echo "\n 未识别的操作 \n";
                exit;
        }
    }

    /**
     * 上报门开启
     * @param int $gear_id 柜体编号
     * @param array $data 数据部分
     * @return mixed
     */
    private function report_door_open($gear_id,$data)
    {
        // 上位机下发：[68][长度低][长度高][68][8B][01][门状态][校验和][16]
        // 示例：68 04 00 68 8B 01 01 01 8E 16
        //
        // 解析：
        // 68 帧头
        // 04 数据长度[低位]
        // 00 数据长度[高位]
        // 68 帧头
        // 8B 上下行：上位机下发，8B：下位机上报
        // 01 操作类别：01：开门操作，03：关门上报/查询操作
        // 01 门编号
        // 01 门状态：01：开门，02：关门，00：无效
        // 8E 校验和
        // 16 结束符

        if (count($data) < 4) {
            echo "\n 开门上报数据长度有误 \n";
            exit;
        }
        // data示例：array('8b','01','01','01');

        $door_state = $data[3] == '01' ? 1 : 0;
        $door_serial = hexdec($data[2]);

        // 更新柜子状态
        Door::where([
            ['door_serial', '=', $door_serial],
            ['gear_id', '=', $gear_id],
        ])->update(['lock_status' => $door_state]);// 1开，0关
        exit;
    }

    /**
     * 关门/查询上报
     * @param int $gear_id 柜体编号
     * @param array $data 数据部分
     */
    private function report_door_status($gear_id,$data)
    {
        // 上位机下发：[68][长度低][长度高][68][8B][03][门编号][门状态][电池数量][电池编号][校验和][16]
        // 示例：68 09 00 68 8B 03 01 02 01 22 63 FC FC 0F 16
        //
        // 解析：
        // 68 帧头
        // 09 数据长度[低位]
        // 00 数据长度[高位]
        // 68 帧头
        // 8B 上下行：4B：上位机下发，8B：下位机上报
        // 03 操作类别：01：开门操作，03：关门上报/查询操作
        // 01 门编号
        // 02 门状态：01：开门，02：关门，00：无效
        // 01 电池数量
        // 22 63 FC FC 电池编码
        // 0F 校验和
        // 16 结束符

        if (count($data) < 5) {
            echo "\n 关门上报数据长度有误 \n";
            exit;
        }

        $door_serial = hexdec($data[2]);
        $door_state = $data[3] == '01' ? 1 : 0;
        $device_num = hexdec($data[4]);

        // 获取设备列表
        $device_rfid_list = [];
        for ($i = 0; $i < $device_num * 4; $i += 4) {
            $device_rfid_list[] = implode('',array_slice($data,5 + $i,4));
        }
        $res = $this->closeDoor($gear_id,$door_serial,$device_rfid_list,$door_state);

        if (!$res) echo "\n" . $this->getError() . "\n";

        exit;
    }

    /**
     * 关柜门
     * @param int $gear_id 柜体ID
     * @param int $door_serial 柜门编号
     * @param string|array $device_rfid_list 柜子中设备rfid列表
     * @param int $door_state 门锁状态
     * @return bool
     */
    private function closeDoor($gear_id,$door_serial, $device_rfid_list, $door_state)
    {
        // 将设备rfig列表格式化为数组
        $arr_device_rfid_list = is_array($device_rfid_list) ? $device_rfid_list : explode(',', $device_rfid_list);

        // 获取柜子当前实际设备数量
        $num = count($arr_device_rfid_list);
        $door_status = ($num >= Door::$max_device) ? 2 : (0 == $num ? 0 : 1); // 状态:1正常,0空,2满

        // 更新柜子状态，得到最后操作用户
        $door = Door::where([
            'door_serial' => $door_serial,
            'gear_id' => $gear_id
        ])->find();
        if (!$door) {
            $this->error = '上报的柜子未在系统中记录';
            return false;
        }

        $door_isupdate = false;
        // 柜门状态有变化，则更新柜门状态
        if ($door->lock_status != $door_state || $door->status != $door_status) {
            $door_isupdate = true;
        }
        $door->lock_status = $door_state; // 0关,1开
        $door->status = $door_status;
        $door->save();


        $last_operator_id = $door->last_operator_id; // 读取柜子最后操作人id
        if (empty($last_operator_id)) {
            // 未找到最后操作人，默认为管理员
            $last_operator_id = $door->last_operator_id = 1;
            $door->last_operator_isadmin = 1;
            $door->last_operation = 1;
        }

        $last_operator_isadmin = $door->last_operator_isadmin; // 最后操作人是否是管理员
        $last_operation = $door->last_operation; // 最后开柜意图:1使用设备,2设备安检,3其他

        // 检查此操作前该柜子中的设备
        $device = new Device();
        $device_before = $device->where('door_id', $door->id)->where('rfid is not null')->column('rfid');
        $before_device_list = empty($device_before) ? [] : $device_before;  // 操作前的设备
        $arr_device_rfid_list = empty($arr_device_rfid_list) ? [] : $arr_device_rfid_list;// 当前设备

        // 数据转小写
        $before_device_list = array_change_key_case(array_flip($before_device_list));
        $before_device_list = array_flip($before_device_list);

        $arr_device_rfid_list = array_change_key_case(array_flip($arr_device_rfid_list));
        $arr_device_rfid_list = array_flip($arr_device_rfid_list);

        // 检查此次关门时减少的设备(通过iris标签检查)
        $device_out = array_diff($before_device_list, $arr_device_rfid_list); // [ rfid => device_id ]
        // 检查此次关门时增加的设备(通过iris标签检查)
        $device_in = array_diff($arr_device_rfid_list, $before_device_list); // [ rfid => device_id ]

        // 有设备取出
        if (!empty($device_out)) {
            switch ($last_operation) {
                case 1 : $device_new_status = 1; break; // 使用设备
                case 2 : $device_new_status = 4; break; // 设备安检
                default: $device_new_status = 1;        // 不明原因的取出设备按照使用处理
            }
            $this->deviceOut(array_values($device_out), $device_new_status, $door->id, $last_operator_id, $last_operator_isadmin);
        }

        // 有设备放入
        if (!empty($device_in)) {
            $this->deviceIn(array_values($device_in), $door->id, $last_operator_id, $last_operator_isadmin);
        }
        // 非后台操作且柜门状态变化了，则记录用户开/关门
        if (!$last_operator_isadmin && $door_isupdate) {
            MemberRecord::create([
                'uid' => $door->last_operator_id,
                'type' => $door_state,
                'gear_door' => $door->id,
            ]);
        }

        return true;
    }

    /**
     * 取出设备
     * @param array $device_rfid_list 设备rfidID列表
     * @param int $device_status 设备状态 :1使用中,2充电中,3空闲中,4安检中,5超时未安检,6已报废
     * @param int $door_id 柜门ID
     * @param int $last_operator_id 最后开门用户id
     * @param int $isadmin 是后台操作，默认false
     */
    private function deviceOut($device_rfid_list, $device_status, $door_id, $last_operator_id, $isadmin)
    {
        // 更新设备状态
        $device = Device::whereIn('rfid',$device_rfid_list)->all();

        foreach ($device as $dev_item) {
            $dev_item->status = $device_status;
            $dev_item->door_id = 0;
            $dev_item->save();

            // 非后台操作，则记录用户操作
            if (!$isadmin) {
                GearRecord::create([
                    'uid' => $last_operator_id,
                    'action' => 1, // 操作，1：取用，2：归还
                    'device' => $dev_item->id,
                    'gear_door' => $door_id,
                    'action_time' => time()
                ]);
                // 创建设备取用记录
                db('used_record')->insert([
                    'device' => $dev_item->id,
                    'get_uid' => $last_operator_id,
                    'get_time' => time(),
                ]);
            }
        }
        // 非后台操作，则更新用户持有的设备列表
        if (!$isadmin) {
            $user = User::get($last_operator_id);
            $use_device_list = trim($user->device_list, ',');
            $arr_use_device_list = empty($use_device_list) ? [] : explode(',', $use_device_list);

            // 用户追加设备列表
            $new_use = array_column($device->toArray(),'id');
            foreach ($new_use as $nu_i) {
                if (!in_array($nu_i,$arr_use_device_list))
                    array_push($arr_use_device_list,$nu_i);
            }
            $user->device_list = implode(',', $arr_use_device_list);
            $user->save();
        }
        return true;
    }

    /**
     * 放入设备
     * @param array $device_rfid_list 设备ID列表
     * @param int $door_id 柜门ID
     * @param int $last_operator_id 最后开门用户id
     * @param int $isadmin 是后台操作，默认false
     */
    private function deviceIn($device_rfid_list, $door_id, $last_operator_id, $isadmin)
    {
        // 如果有设备放入，且柜子最后充电时间未到，则是给设备充电，更新柜子最后充电时间
        $charging = 0;
        $door = Door::get($door_id);
        if ($door->last_battery_full > time()) {
            $door->last_battery_full = strtotime(date('Y-m-d H:i:s') . ' + 8 hours');
            $door->save();
            $charging = 1;
        }

        // 更新设备状态
        $device = Device::whereIn('rfid',$device_rfid_list)->all();
        foreach ($device as $dev_item) {
            // 状态:1使用中,2充电中,3空闲中,4安检中,5超时未安检,6已报废
            $status = $charging ? 2 : ($door->is_security ? 4 : 3);
            $dev_item->status = $status;
            $dev_item->door_id = $door_id;
            $dev_item->save();

            // 非后台操作，则记录用户操作
            if (!$isadmin) {
                GearRecord::create([
                    'uid' => $last_operator_id,
                    'action' => 2, // 操作，1：取用，2：归还
                    'device' => $dev_item->id,
                    'gear_door' => $door_id,
                    'action_time' => time()
                ]);

                // 查询设备取用记录
                $check_used_record = db('used_record')->where('device', $dev_item->id)->order('id', 'desc')->find();

                // 有该设备取用记录，则更新归还记录
                if (!empty($check_used_record) && empty($check_used_record['back_uid'])) {
                    db('used_record')->where('id', $check_used_record['id'])->update([
                        'back_uid' => $last_operator_id,
                        'back_time' => time(),
                    ]);
                }else{
                    // 没有取用记录，则直接创建取用、归还记录
                    db('used_record')->insert([
                        'device' => $dev_item->id,
                        'get_uid' => $last_operator_id,
                        'get_time' => time(),
                        'back_uid' => $last_operator_id,
                        'back_time' => time(),
                    ]);
                }
            }
        }

        // 从用户持有的设备列表中移除这些设备
        foreach ($device as $dev_i) {
            $dev_id = $dev_i->id;
            $users = User::where("find_in_set($dev_id,`device_list`)")->select();
            foreach ($users as $user) {
                // 查询当前列表
                $curr_device_list = trim($user->device_list, ',');
                $arr_curr_device_list = empty($curr_device_list) ? [] : explode(',', $curr_device_list);
                // 减去还回的
                $new_list = array_diff($arr_curr_device_list, [$dev_id]);
                // 更新记录
                $user->device_list = implode(',', $new_list);
                $user->save();
            }
        }
    }

    /**
     * 开门操作
     * @param mixed $gear_serial 柜体编号
     * @param int $door_serial 柜门编号
     * @param string $userName 用户姓名
     * @param string $userNo 用户编号
     */
    private function open($gear_serial,$door_serial, $userName, $userNo)
    {
        // 上位机下发：[68][长度低][长度高][68][4B][01][门编号][姓名长度][姓名数据][工号长度][工号数据][校验和][16]
        // 示例：68 0E 00 68 4B 01 01 04 5F 20 4E 09 05 31 30 30 30 31 1E 16
        //
        // 解析：
        // 68 帧头
        // 0E 数据长度[低位]
        // 00 数据长度[高位]
        // 68 帧头
        // 4B 上下行：4B：上位机下发，8B：下位机上报
        // 01 操作类别：01：开门操作，03：关门上报/查询操作
        // 01 门编号
        // 04 姓名长度
        // 5F 20 4E 09 姓名数据：张三
        // 05 工号长度
        // 31 30 30 30 31 工号数据：10001
        // 1E 校验和
        // 16 结束符

        // 柜门编号：01
        $door_serial = str_pad(dechex($door_serial), 2, 0, STR_PAD_LEFT);

        // 姓名转Unicode编码: 张三：5f204e09
        $nameHex = $this->strToGBK($userName);

        // 姓名长度：04
        $nameLen = str_pad(strlen($nameHex) / 2, 2, 0, STR_PAD_LEFT);

        // 用户工号长度：10001 ：05
        $userSerialLen = str_pad(strlen($userNo), 2, 0, STR_PAD_LEFT);

        // 工号转数字字符，十六进制ASCII：3130303031
        $userSerialHex = bin2hex((string)$userNo); // 工号数据

        $build = '4B01' . $door_serial . $nameLen . $nameHex . $userSerialLen . $userSerialHex;

        return $this->sendMsg($gear_serial,$build);
    }

    /**
     * 发送消息
     * @param string $gear_serial 柜体编号
     * @param string $data 数据部分字符串
     * @param bool $program_check 验证程序状态（回复消息请传false）
     * @return bool
     */
    private function sendMsg($gear_serial,$data, $program_check = true)
    {
        $data = str_replace(' ','',$data);
        // 计算数据部分长度，拼接方式：[长度低][长度高]
        $data_len = strlen($data) / 2;
        $Hexlen = base_convert($data_len, 10, 16);
        $Hexlen = str_pad($Hexlen, 4, '0', STR_PAD_LEFT);
        $len_arr = str_split($Hexlen, 2);
        krsort($len_arr);
        $data_len = implode($len_arr);

        // 数据校验和
        $check = $this->checksum($data);

        // [68][长度低][长度高][68] ...[data]... [check][16]
        $code = '68' . $data_len . '68' . $data . $check . '16';
        dta($code,'准备发送指令');

        // 数据检查
        if (!$this->dataCheck($code)) return false;

        // 将指令以十六进制身份转为二进制字符串
        $temp = hex2binarystring($code);

        // 将二进制字符转为ASCII字符串
        $msg = bin2chr($temp);

        // 串口通信程序接口地址
        $serial_path = dirname(dirname(dirname(__DIR__))) . '/extend/serial/'.$gear_serial;

        $serial_path = str_replace('\\', '/', $serial_path);
        $msg_file = $serial_path.'/conf/send_msg';

        // 串口通信程序状态检查
        if ($program_check && !$this->serialProgramCheck($serial_path)) {
            $this->error = '串口通信程序未正确运行';
            return false;
        }

        // 将指令写入串口通信程序接口文件中，该程序能自动读取并发送指令
        $fp = fopen($msg_file, 'w');
        fwrite($fp, $msg);
        fclose($fp);
        dta($code,'指令发送成功');
        return true;
    }

    /**
     * 检查串口通信程序
     * @param string $serial_path 通信文件路径
     * @return bool
     */
    private function serialProgramCheck($serial_path)
    {
        $msg_file = $serial_path.'/conf/send_msg';

        // 通信文件存在，则删除
        if (is_file($msg_file)) unlink($msg_file);
        usleep(200000); // 删除完等待0.2秒

        // 如果通信文件被正常创建，则程序正常
        if (is_file($msg_file)) return true;

        // 检查通信程序启动文件
        if (!is_file($serial_path . '/start.vbs')) {
            $this->error = '通讯程序丢失';
            return false;
        }

        $cmd = "cd $serial_path && start start.vbs && exit 2>&1";
        exec($cmd);
        usleep(200000); // 删除完等待0.2秒
        if (is_dir($serial_path)) deleteDir("$serial_path/%SystemDrive%");

        // 再次检查通信文件是否被正常创建
        if (is_file($msg_file)) return true;

        $this->error = '串口通信程序未正确运行';
        return false;
    }

    /**
     * 校验消息并返回数据部分
     * @param string $msg 消息字符串
     * @return array|bool
     */
    private function dataCheck($msg)
    {
        $msg = strtolower($msg);
        if (empty($msg)) {
            $this->error = '消息为空';
            return false;
        }
        $arrMsg = str_split($msg, 2);

        // 校验
        $real_msglen = count($arrMsg);     // 消息总长度
        $real_datelen = $real_msglen - 6;  // 消息数字部分长度 去掉 [68][len][len][68] ... [ck][16]

        // 校验数据结构
        if ($real_datelen < 1 || $arrMsg[0] != '68' || $arrMsg[3] != '68' || $arrMsg[$real_msglen - 1] != '16') {
            $this->error = '数据结构不正确';
            return false;
        }

        // 对比数据长度校验值
        $told_datalen = hexdec($arrMsg[2] . $arrMsg[1]); // 告知的数据部分长度
        if ($told_datalen != $real_datelen) {
            $this->error = '数据长度校验值不通过: 得到数据长度 = ' . $told_datalen . ', 实际校验长度 = ' . $real_datelen;
            return false;
        }

        $arrData = str_split(mb_substr($msg, 8, -4), 2);

        // 对比数据校验值
        $real_datacheck = 0;
        foreach ($arrData as $k => $v) {
            $real_datacheck += hexdec($v); // 计算数据各项之和
        }
        $real_datacheck = mb_substr(dechex($real_datacheck), -2); // 取低位1字节
        if ($real_datacheck != $arrMsg[$real_msglen - 2]) {
            $this->error = '数据值校验值不通过: 得到校验值 = ' . $arrMsg[$real_msglen - 2] . ', 实际校验值 = ' . $real_datacheck;
            return false;
        }
        return $arrData;
    }

    /**
     * 字符串转Unicode编码
     * @param $strLong
     * @return string
     */
    private function UnicodeEncode($strLong)
    {
        $strArr = preg_split('/(?<!^)(?!$)/u', $strLong);//拆分字符串为数组(含中文字符)
        $resUnicode = '';
        foreach ($strArr as $str) {
            $bin_str = '';
            $arr = str_split($str);//获取字符内部数组表示,此时$arr应类似array(228, 189, 160)
            foreach ($arr as $value) {
                $bin_str .= decbin(ord($value));//转成数字再转成二进制字符串,$bin_str应类似111001001011110110100000,如果是汉字"你"
            }
            $bin_str = preg_replace('/^.{4}(.{4}).{2}(.{6}).{2}(.{6})$/', '$1$2$3', $bin_str);//正则截取, $bin_str应类似0100111101100000,如果是汉字"你"
            $unicode = dechex(bindec($bin_str));//返回unicode十六进制
            $resUnicode .= $unicode;
        }
        return $resUnicode;
    }

    /**
     * 字符串转GBK编码
     * @param $strLong
     * @return string
     */
    private function strToGBK($strLong)
    {
        //获取其字符的内部数组表示，所以本文件应用utf-8编码！
        if (is_array($strLong)) {
            $arr = $strLong;
        } else {
            $word = iconv('utf-8', 'gbk', $strLong);
            $arr = str_split($word);
        }
        $bin_str = '';
        foreach ($arr as $value) {
            $bin_str .= dechex(bindec(decbin(ord($value))));
        }
        return $bin_str;
    }

    /**
     * 创建校验和结束符
     * @param $str
     * @return string
     */
    private function checksum($str)
    {
        $xor = substr($str, 0, 2);
        $xor = hexdec($xor);
        for ($i = 2; $i < strlen($str); $i += 2) {
            $ccc = substr($str, $i, 2);
            $xor = $xor + hexdec($ccc);
        }
        return substr(dechex($xor), -2);
    }
}
